package roborally.Gui;

import java.io.IOException;
import java.io.Writer;
import java.util.*;

import be.kuleuven.cs.som.annotate.*;

import roborally.Board;
import roborally.Pieces.*;
import roborally.util.CommandParser;
import roborally.util.MoveOutOfBoardException;
import roborally.util.Program;

/**
 * This is a class for the RoboRally game with boards. It represents the interface between the internal program and the GUI. 
 * 
 * @version 3.0
 * @author Bart Opsomer 2de Bachelor Ingenieurswetenschappen Electrotechniek-Computerwetenschappen 
 * 		   Peter Opsomer 1ste Master Wiskundige IngenieursTechnieken
 */

//TODO The header of the class Facade should look as follows:class Facade implements IFacade<BoardImpl, RobotImpl, WallImpl, BatteryImpl, 
// RepairKitImpl, SurpriseBoxImpl> { ... } dus <Board, Robot, Wall, Battery, RepairKit, SurpriseBox>  ??
public class Facade implements IFacade<Board, Robot, Wall, Battery, RepairKit, SurpriseBox> {
	
	private Set<Board> boards;
	/**
	 * Creates a facade with a board.
	 * @param board the board to be played on
	 */
	public Facade(Board board) {
		boards = new HashSet<Board>();
		boards.add(board);
	}
	/**
	 * @effect this(new Board(100L,100L))
	 */
	public Facade() {
		this(new Board(100L,100L));
	}
	/**
	 * Create a new board.
	 * @param width the width of the board
	 * @param height the height of the board
	 * @return 	the created board if the given dimensions are valid
	 * 			| if( isValidDimension(width, height)
	 * 			|  then result = new Board(width, height);
	 */
	@Override
	public Board createBoard(long width, long height) {
		try {
			Board board = new Board(width, height);
			boards.add(board);
			return board;
		}
		catch(IllegalArgumentException e){
			System.out.println("The given dimensions of the board are not valid");
			return null;
		}
	}
	/**
	 * Merge board2 into board1.
	 * @param board1 the board into which board2 is merged
	 * @param board2 the board which is merged into board1
	 * @effect board1.merge(board2)
	 */
	@Override
	public void merge(Board board1, Board board2) {
		// put all object from board 2 in 1 if possible, Board.merge doesn't throw Exceptions.
		board1.merge(board2);
	}
	/**
	 * Creates a new Battery with given initial energy in Ws and weight in g and maxEnergy of 5000Ws.
	 * @param initialEnergy the initial energy of the battery in Ws
	 * @param weight the weight of the battery in g
	 * @return result = new Battery(weight,new Energy(initialEnergy),new Energy(5000));
	 */
	@Override
	public Battery createBattery(double initialEnergy, int weight) {
		if(initialEnergy < 0) {
			return null;
		}
		Battery battery = new Battery(weight,new Energy(initialEnergy),new Energy(5000));
		return battery;
	}
	/**
	 * Put the given Battery on the given board at position (x,y).
	 * @param board the board onto which the Battery is put
	 * @param x the x-coordinate of the battery on the board
	 * @param y the y-coordinate of the battery on the board
	 * @param battery the battery to be put on the board
	 * @effect board.addItem(Position.getPosition(x,y,board), battery)
	 */
	@Override
	public void putBattery(Board board, long x, long y, Battery battery) {
		try{
			board.addItem(Position.getPosition(x,y,board), battery);
		}
		catch(MoveOutOfBoardException exc) {
			// Do nothing
		}
	}

	/**
	 * Return the x-coordinate of the battery.
	 * @param battery the battery in question
	 * @return result = battery.getPosition().getX()
	 * @throws 	IllegalStateException
	 * 			| if(battery.getPosition() == null)
	 */
	@Override @Basic
	public long getBatteryX(Battery battery) throws IllegalStateException {
		if(battery.getPosition() == null)
			throw new IllegalStateException();
		return battery.getPosition().getX();
	}
	
	/**
	 * Return the y-coordinate of the battery
	 * @param battery the battery in question
	 * @return result = battery.getPosition().getY()
	 * @throws 	IllegalStateException
	 * 			| if(battery.getPosition() == null)
	 */
	@Override @Basic
	public long getBatteryY(Battery battery) throws IllegalStateException {
		if(battery.getPosition() == null)
			throw new IllegalStateException();
		return battery.getPosition().getY();
	}
	/**
	 * Creates a new Robot with the given initialEnergy
	 * @param initialEnergy the initial energy of the robot in Ws
	 * @return the Robot has the orientation corresponding the given orientation and result = new Robot(new Energy(initialEnergy))
	 */
	@Override 
	public Robot createRobot(int orientation,double initialEnergy) {
		if(initialEnergy < 0) {
			return null;
		}
		Orientation myOrientation = Orientation.getOrientationFromInt(orientation);
		PositionWithOrientation orientationWithoutPosition = PositionWithOrientation.getOrientationWithoutPosition(myOrientation);
		Robot robot = new Robot(new Energy(initialEnergy));
		robot.setPositionWithOrientation(orientationWithoutPosition);
		return robot;
	}

	/**
	 * Put the robot on board at (x,y) with orientation Orientation(orientation)
	 * @param board the board onto which to robot is to be placed
	 * @param x the x-coordinate of the battery on the board 
	 * @param y the y-coordinate of the battery on the board
	 * @param robot the robot in question
	 * @param orientation the orientation of the robot
	 * @post	| (new board ).contains(robot)
	 */
	@Override
	public void putRobot(Board board, long x, long y, Robot robot) {
		Orientation orientation = robot.getPositionWithOrientation().getOrientation();
		try{
			PositionWithOrientation pos = PositionWithOrientation.getPWO(x,y,board,orientation);
			board.addRobot(robot,pos);
		}
		catch(MoveOutOfBoardException excp) { }
		catch(IllegalArgumentException excp) { }
	}
	/**
	 * Return the x-coordinate of the robot.
	 * @param battery the robot in question
	 * @return  |result = robot.getPosition().getX()
	 * @throws 	IllegalStateException
	 * 			| if(battery.getPosition() == null)
	 */
	@Override @Basic
	public long getRobotX(Robot robot) throws IllegalStateException {
		if(robot.getPosition() == null)
			throw new IllegalStateException();
		return robot.getPosition().getX();
	}
	/**
	 * Return the y-coordinate of the robot.
	 * @param battery the robot in question
	 * @return 	|result = robot.getPosition().getY()
	 * @throws 	IllegalStateException
	 * 			| if(battery.getPosition == null)
	 */
	@Override @Basic
	public long getRobotY(Robot robot) throws IllegalStateException {
		if(robot.getPosition() == null)
			throw new IllegalStateException();
		return robot.getPosition().getY();
	}
	/**
	 * Returns the orientation of the robot as an integer.
	 * @param robot
	 * @return result = robot.getPositionWithOrientation().getOrientation().getInt()
	 */
	@Override @Basic
	public int getOrientation(Robot robot) {
		if(robot.getPositionWithOrientation() == null) {
			return 31415926;
		}
		return robot.getPositionWithOrientation().getOrientation().getInt();
	}
	/**
	 * Returns the current energy of the robot in Ws.
	 * @param robot the robot of which the energy is requested
	 * @return robot.getEnergy().getAmount()
	 */
	@Override @Basic
	public double getEnergy(Robot robot) {
		return robot.getEnergy().getAmountInWattSeconds();
	}
	/**
	 * Move the robot 1 step forward.
	 * @param robot the robot to be moved
	 * @effect robot.getPosition().getBoard().moveRobot(robot)
	 */
	@Override
	public void move(Robot robot) {
		Board myBoard = robot.getPosition().getBoard();
		if(robot.getEnergy().isGreaterThanOrEquals(robot.getMoveEnergy() ) ) {
			myBoard.moveRobot(robot);
		}
	}
	/**
	 * Turn the robot clockWise.
	 * @param robot the robot to be turned
	 * @effect robot.turnClockwise();
	 */
	@Override
	public void turn(Robot robot) {
		if(robot.getEnergy().isGreaterThanOrEquals(robot.getTurnEnergy() ) ) {
			robot.turnClockwise();
		}
	}
	/**
	 * Pick up the given item if possible.
	 * @param robot the robot to pick up the item
	 * @param item the item to be picked up
	 * @post  	|if(!robot.isTerminated() && !item.isTerminated() )
	 * 			|	then new robot.getInventory().contains(item)
	 */
	@Override
	public void pickUpBattery(Robot robot, Battery battery) {
		if(robot == null || battery == null)
			return;
		if(robot.isTerminated() || battery.isTerminated())
			return;
		if(robot.getPosition().equals(battery.getPosition())) {
			robot.pickupItem(battery);
		}
		else {
			System.out.println("Robot is at "+robot.getPosition().toString() + 
					"and the object you want to pickup is at"+battery.getPosition().toString());
		}
		
	}
	/**
	 * Use the item if the robot has it in its inventory.
	 * @effect  robot.use(item)
	 */
	@Override
	public void useBattery(Robot robot, Battery battery) {
		if(robot.getInventory().contains(battery)) {
			robot.use(battery);
		}
		else {
			System.out.println("The robot does not have that Battery");
		}
	}
	/**
	 * Let the robot drop the given item if the robot has it in its inventory.
	 * @effect	robot.drop(item)
	 */
	@Override
	public void dropBattery(Robot robot, Battery battery) {
		robot.drop(battery);
	}
	/**
	 * @return | result = 1
	 */
	@Override @Immutable
	public int isMinimalCostToReach17Plus() {
		return 1;
	}
	
	/**
	 * Returns the minimal energy for the robot to reach (x,y) on the board of the robot.
	 * @return 	the minimal energy for the robot to reach (x,y) on the board of the robot
	 * 			| result = myRobot.getEnergyRequiredToReach(Position.getPosition(x,y,board) )
	 */
	@Override
	public double getMinimalCostToReach(Robot robot, long x, long y) {
		Board board = robot.getPosition().getBoard();
		try{
			return robot.getEnergyRequiredToReach(Position.getPosition(x,y,board) );
		}
		catch(MoveOutOfBoardException exc) {
			System.out.println("The given point (x,y) is not a valid location");
			return 0.0;
		}
		catch(IllegalStateException exc) {
			System.out.println("The robot is terminated, and cannot move");
			return 0.0;
		}
		catch(Exception e) {
			// not possible
			System.err.println("Exception: " + e);
			return 0.0;
		}
	}
	/**
	 * @return 	| result = 1
	 */
	@Override @Immutable
	public int isMoveNextTo18Plus() {
		return 1;
	}
	/**
	 * Move the 2 robots as close as possible with the minimal energy.
	 * @effect  robot.moveNextTo(other)
	 */
	@Override
	public void moveNextTo(Robot robot, Robot other) {
		try {
			robot.moveNextTo(other);
		}
		catch(Exception e) {
			System.err.println("Exception: " + e);
		}
	}
	/**
	 * Let the robot shoot in the direction of the robot.
	 * @effect 	robot.getPosition().getBoard().shoot(robot)
	 */
	@Override
	public void shoot(Robot robot){
		Board board = robot.getPosition().getBoard();
		// Precondition for shootEnergy is checked in Board.shoot
		board.shoot(robot);
	}
	/**
	 * Create a new wall.
	 * @return 	returns a new wall
	 * 			| result = new Wall()
	 */
	@Override
	public Wall createWall() {
		Wall wall = new Wall();
		return wall;
	}
	/**
	 * Put the wall on this board at (x,y)
	 * @effect	board.addWall(wall.setPosition(Position.getPosition(x,y,myBoard)))
	 */
	@Override
	public void putWall(Board board, long x, long y, Wall wall) {
		try{
			board.addWall(wall,Position.getPosition(x,y,board));
		}
		catch(Exception e) {
			System.err.println("Exception: " + e);
		}
	}
	/**
	 * Returns the x-coordinate of the given wall
	 * @return 	the x-coordinate of the given wall
	 * 			| result = wall.getPosition().getX()
	 * @throws	IllegalStateException
	 * 			| if(wall.getPosition() ==null)
	 */
	@Override @Basic
	public long getWallX(Wall wall) throws IllegalStateException {
		Position pos = wall.getPosition();
		if(pos == null)
			throw new IllegalStateException();
		return pos.getX();
	}
	/**
	 * Returns the y-coordinate of the given wall.
	 * @return 	the y-coordinate of the given wall
	 * 			| result = wall.getPosition().getX()
	 * @throws	IllegalStateException
	 * 			| if(theWall.getPosition() ==null)
	 */
	@Override @Basic
	public long getWallY(Wall wall) throws IllegalStateException {
		Position pos = wall.getPosition();
		if(pos == null)
			throw new IllegalStateException();
		return pos.getY();
	}
	/**
	 * Get all robots on the given board.
	 * @return	all robots on the board
	 * 			| result = board.getRobots()
	 */
	@Override
	public Set<Robot> getRobots(Board board) {
		Set<Robot> robots = board.getRobots();
		return new HashSet<Robot>(robots);
	}
	/**
	 * Get all batteries on the given board
	 * @return	all robots on the board
	 * 			| result = board.getBatteries()
	 */
	@Override
	public Set<Battery> getBatteries(Board board) {
		Set<Battery> batteries = board.getBatteries();
		return new HashSet<Battery>(batteries);
	}
	/**
	 * Get all walls on the given board
	 * @return	all walls on the board
	 * 			| result = board.getWalls()
	 */
	@Override
	public Set<Wall> getWalls(Board board) {
		Set<Wall> walls = board.getWalls();
		return new HashSet<Wall>(walls);
	}
	@Override
	public RepairKit createRepairKit(double repairAmount, int weight) {
		if(repairAmount < 0) {
			return null;
		}
		RepairKit kit = new RepairKit(new Energy(repairAmount), weight);
		return kit;
	}
	@Override
	public void putRepairKit(Board board, long x, long y, RepairKit repairKit) {
		try{
			Position position = Position.getPosition(x, y, board);
			board.addItem(position, repairKit);
		}
		catch(Exception e) {
			System.err.println("Exception: " + e);
		}
	}
	@Override
	public long getRepairKitX(RepairKit repairKit) throws IllegalStateException {
		if(repairKit.getPosition() == null) {
			throw new IllegalStateException();
		}
		return repairKit.getPosition().getX();
	}
	@Override
	public long getRepairKitY(RepairKit repairKit) throws IllegalStateException {
		if(repairKit.getPosition() == null) {
			throw new IllegalStateException();
		}
		return repairKit.getPosition().getY();
	}
	@Override
	public SurpriseBox createSurpriseBox(int weight) {
		SurpriseBox suprisebox = new SurpriseBox(weight);
		return suprisebox;
	}
	@Override
	public void putSurpriseBox(Board board, long x, long y, SurpriseBox surpriseBox) {
		try{
			Position position = Position.getPosition(x, y, board);
			board.addItem(position, surpriseBox);
		}
		catch(Exception e) {
			System.err.println("Exception: " + e);
		}
	}
	@Override
	public long getSurpriseBoxX(SurpriseBox surpriseBox) throws IllegalStateException {
		if(surpriseBox.getPosition() == null) {
			throw new IllegalStateException();
		}
		return surpriseBox.getPosition().getX();
	}
	@Override
	public long getSurpriseBoxY(SurpriseBox surpriseBox) throws IllegalStateException {
		if(surpriseBox.getPosition() == null) {
			throw new IllegalStateException();
		}
		return surpriseBox.getPosition().getY();
	}
	@Override
	public void pickUpRepairKit(Robot robot, RepairKit repairKit) {
		if(robot == null || repairKit == null)
			return;
		if(robot.isTerminated() || repairKit.isTerminated())
			return;
		if(robot.getPosition().equals(repairKit.getPosition())) {
			robot.pickupItem(repairKit);
		}
		else {
			System.out.println("Robot is at "+robot.getPosition().toString() + 
					"and the object you want to pickup is at"+repairKit.getPosition().toString());
		}
		
	}
	@Override
	public void useRepairKit(Robot robot, RepairKit repairKit) {
		if(robot.getInventory().contains(repairKit)) {
			robot.use(repairKit);
		}
		else {
			System.out.println("The robot does not have that RepairKit");
		}
	}
	@Override
	public void dropRepairKit(Robot robot, RepairKit repairKit) {
		robot.drop(repairKit);
	}
	@Override
	public void pickUpSurpriseBox(Robot robot, SurpriseBox surpriseBox) {
		if(robot == null || surpriseBox == null)
			return;
		if(robot.isTerminated() || surpriseBox.isTerminated())
			return;
		if(robot.getPosition().equals(surpriseBox.getPosition())) {
			robot.pickupItem(surpriseBox);
		}
		else {
			System.out.println("Robot is at "+robot.getPosition().toString() + 
					"and the object you want to pickup is at"+surpriseBox.getPosition().toString());
		}
	}
	@Override
	public void useSurpriseBox(Robot robot, SurpriseBox surpriseBox) {
		if(robot.getInventory().contains(surpriseBox)) {
			robot.use(surpriseBox);
		}
		else {
			System.out.println("The robot does not have that SurpriseBox");
		}
	}
	@Override
	public void dropSurpriseBox(Robot robot, SurpriseBox surpriseBox) {
		robot.drop(surpriseBox);
	}
	/**
	 * Send all items from the 'from' object to the 'to' object.
	*/
	@Override
	public void transferItems(Robot from, Robot to) {
		from.transferAllItemsTo(to);
	}
	@Override
	public Set<RepairKit> getRepairKits(Board board) {
		return board.getRepairKits();
	}
	@Override
	public Set<SurpriseBox> getSurpriseBoxes(Board board) {
		return board.getSupriseBoxes();
	}
	@Override
	public int loadProgramFromFile(Robot robot, String path) {
		try {
			CommandParser parser = Program.loadProgramForRobot(robot, path);
			robot.setParser(parser);
			return 0;
		}
		catch (Exception e) {
			return -1;
		}
	}
	@Override
	public int saveProgramToFile(Robot robot, String path) {
		try {
			Program.saveProgram(path, robot.getParser().getProgram());
			return 0;
		}
		catch (Exception e) {
			return -1;
		}
	}
	@Override
	public void prettyPrintProgram(Robot robot, Writer writer) {
		String program = robot.getParser().getProgram();
		try {
			writer.write(program);
		} catch (IOException e) {
			System.out.println("Cannot print program");
		}
		
	}
	@Override
	public void stepn(Robot robot, int n) {
		CommandParser parser = robot.getParser();
		parser.executeN(n);
	}

}
